#include "json/json.h"
#include "MetaObjectIO.h"
#include "PublicFunction.h"
#include "MetaObjectTrans.h"

namespace gitstore {

CMetaDataIOBase::CMetaDataIOBase() {
    _trans_op=std::make_shared<CObjectCodeBoostSerializationTrans>();
}

//CMockMetaDataIO::CMockMetaDataIO() {

//}

//CMockMetaDataIO::~CMockMetaDataIO() {

//}

//ErrorStatus CMockMetaDataIO::begin(const string& obj_name) {
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::commit(const string& obj_name) {
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::rollback(const string& obj_name) {
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::loadNode(const string& obj_name, const string& version, Node& node) {
//    if ( _meta_store.find(obj_name) != _meta_store.end() ) {
//        auto &mdataset = _meta_store[obj_name];
//        if ( mdataset.find(version) != mdataset.end() ) {
//            decodeNode(node, mdataset[version]);

//        }
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::saveNode(const Node& node) {
//    if ( _meta_store.find(node.obj_name) != _meta_store.end() ) {
//        auto &mdataset = _meta_store[node.obj_name];
//        std::string str;
//        encodeNode(node, str);
//        mdataset[node.version] = str;
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::loadBranch(const string& obj_name, const string& br_name, Branch& branch) {
//    if ( _meta_store.find(obj_name) != _meta_store.end() ) {
//        auto mdataset = _meta_store[obj_name];
//        if ( mdataset.find(br_name) != mdataset.end() ) {
//            decodeBranch(branch, mdataset[br_name]);

//        }
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::saveBranch(const Branch& branch) {
//    if ( _meta_store.find(branch.obj_name) != _meta_store.end() ) {
//        auto &mdataset = _meta_store[branch.obj_name];
//        std::string str;
//        encodeBranch(branch, str);
//        mdataset[branch.name] = str;
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::loadObject(const string& obj_name, Object& dataset) {
//    if ( _meta_store.find(obj_name) != _meta_store.end() ) {
//        auto mobject = _meta_store[obj_name];
//        if ( mobject.find("dataset") != mobject.end() ) {
//            this->decodeObject(dataset, mobject["dataset"]);
//        }
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::saveObject(const Object& object) {
//    //            if ( _meta_store.find(dataset.name) != _meta_store.end() ) {
//    //                auto mdataset = _meta_store[dataset.name]["dataset"] = ;
//    std::string str;
//    this->encodeObject(object, str);
//    _meta_store[object.name]["dataset"] = str;
//    //            }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::listObject(vector<string>& objects) {
//    objects.clear();
//    objects.shrink_to_fit();
//    auto iter = _meta_store.begin();
//    for ( ; iter!=_meta_store.end(); iter++) {
//        objects.emplace_back(iter->first);
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::encodeObject(const Object& object, std::string &json_string) {
//    Json::Value json_value;
//    json_value["date"] = object.date;
//    json_value["note"] = object.note;
//    json_value["name"] = object.name;
//    json_value["type"] = object.type;
//    json_value["main_branch"] = object.main_branch;
//    json_value["nodes"] = Json::arrayValue;
//    for ( auto &node : object.nodes ) {
//        json_value["nodes"].append(node);
//    }
//    json_value["branches"] = Json::arrayValue;
//    for ( auto &branch : object.branches ) {
//        json_value["branches"].append(branch);
//    }
//    json_string = json_value.toStyledString();
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::decodeObject(Object& object, const std::string &json_string) {
//    Json::Reader reader;
//    Json::Value json_value;
//    reader.parse(json_string, json_value);
//    object.date = json_value["date"].asInt64();
//    object.note = json_value["note"].asString();
//    object.name = json_value["name"].asString();
//    object.type = json_value["type"].asInt();
//    object.main_branch = json_value["main_branch"].asString();
//    object.nodes.clear();
//    object.nodes.shrink_to_fit();
//    for (unsigned int i = 0; i < json_value["nodes"].size(); ++i) {
//        object.nodes.emplace_back(json_value["nodes"][i].asString());
//    }
//    object.branches.clear();
//    object.branches.shrink_to_fit();
//    for (unsigned int i = 0; i < json_value["branches"].size(); ++i) {
//        object.branches.emplace_back(json_value["branches"][i].asString());
//    }
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::encodeBranch(const Branch& branch, std::string &json_string) {
//    Json::Value json_value;
//    json_value["date"] = branch.date;
//    json_value["note"] = branch.note;
//    json_value["name"] = branch.name;
//    json_value["cur_version"] = branch.cur_version;
//    json_value["type"] = branch.type;
//    json_value["obj_name"] = branch.obj_name;
//    json_string = json_value.toStyledString();
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::decodeBranch(Branch& branch, const std::string &json_string) {
//    Json::Reader reader;
//    Json::Value json_value;
//    reader.parse(json_string, json_value);
//    branch.date = json_value["date"].asInt64();
//    branch.note = json_value["note"].asString();
//    branch.name = json_value["name"].asString();
//    branch.cur_version = json_value["cur_version"].asString();
//    branch.type = json_value["type"].asInt();
//    branch.obj_name = json_value["obj_name"].asString();
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::encodeNode(const Node& node, std::string &json_string) {
//    Json::Value json_value;
//    json_value["date"] = node.date;
//    json_value["note"] = node.note;
//    json_value["version"] = node.version;
//    json_value["type"] = node.type;
//    json_value["obj_name"] = node.obj_name;
//    json_value["parents"] = Json::arrayValue;
//    for (auto &parent : node.parents) {
//        json_value["parents"].append(parent);
//    }
//    json_value["children"] = Json::arrayValue;
//    for (auto &child : node.children) {
//        json_value["children"].append(child);
//    }
//    json_string = json_value.toStyledString();
//    return Success;
//}

//ErrorStatus CMockMetaDataIO::decodeNode(Node& node, const std::string &json_string) {
//    Json::Reader reader;
//    Json::Value json_value;
//    reader.parse(json_string, json_value);
//    node.date = json_value["date"].asInt64();
//    node.note = json_value["note"].asString();
//    node.version = json_value["version"].asString();
//    node.type = json_value["type"].asInt();
//    node.obj_name = json_value["obj_name"].asString();
//    node.parents.clear();
//    node.parents.shrink_to_fit();
//    for (unsigned int i = 0; i < json_value["parents"].size(); ++i) {
//        node.parents.emplace_back(json_value["parents"][i].asString());
//    }
//    node.children.clear();
//    node.children.shrink_to_fit();
//    for (unsigned int i = 0; i < json_value["children"].size(); ++i) {
//        node.children.emplace_back(json_value["children"][i].asString());
//    }
//    return Success;
//}


CEtcdMetaDataIO::CEtcdMetaDataIO(const std::string& url, AppMode mode)
    :_etcd_client(url){
    switch ( mode ) {
    case AppMode::Single:
        obj_path = "/objects";
        break;
    case AppMode::Dataset:
        obj_path = "/dataset";
        break;
    default:
        obj_path = "/objects";
        break;
    }
}

CEtcdMetaDataIO::~CEtcdMetaDataIO() {

}

//ErrorStatus CEtcdMetaDataIO::begin(const string& obj_name) {
//    return Success;
//}

//ErrorStatus CEtcdMetaDataIO::commit(const string& obj_name) {
//    return Success;
//}

//ErrorStatus CEtcdMetaDataIO::rollback(const string& obj_name) {
//    return Success;
//}

ErrorStatus CEtcdMetaDataIO::listObject(vector<string>& objects) {
    objects.clear();
    objects.shrink_to_fit();
    auto res = _etcd_client.ls(obj_path).get();
    if ( !res.is_ok() ) {
        return ETCDGetError;
    }
    auto keys = res.keys();
    objects.reserve(keys.size());
    for ( auto &key : keys ) {
        objects.emplace_back(key.substr(9, key.length()));
    }
    return Success;
}

ErrorStatus CEtcdMetaDataIO::listBranches(const string& obj_name, vector<string>& branches) {
    branches.clear();
    branches.shrink_to_fit();
    auto res = _etcd_client.ls(string("/branches")+obj_path+string("/")+obj_name).get();
    if ( !res.is_ok() ) {
        return ETCDGetError;
    }
    auto keys = res.keys();
    branches.reserve(keys.size());
    for ( auto &key : keys ) {
        branches.emplace_back(key.substr(11+obj_name.size(), key.length()));
    }
    return Success;
}

ErrorStatus CEtcdMetaDataIO::listNodes(const string& obj_name, vector<string>& nodes) {
    nodes.clear();
    nodes.shrink_to_fit();
    auto res = _etcd_client.ls(string("/nodes")+obj_path+string("/")+obj_name).get();
    if ( !res.is_ok() ) {
        return ETCDGetError;
    }
    auto keys = res.keys();
    nodes.reserve(keys.size());
    for ( auto &key : keys ) {
        nodes.emplace_back(key.substr(8+obj_name.size(), key.length()));
    }
    return Success;
}

ErrorStatus CEtcdMetaDataIO::createObject(const Object& object, int& revision) {
    string str;
    auto res = _trans_op->encodeObject(object, str);
    if ( Success != res ) {
        return res;
    }
    auto etcd_res = _etcd_client.add(obj_path+string("/")+object.name/*+string("/info")*/, str).get();
    if ( !etcd_res.is_ok() ) {
        return ETCDAddError;
    }
    revision = etcd_res.value().created_index();
    return Success;
}

ErrorStatus CEtcdMetaDataIO::saveObject(const Object& object, int revision) {
    string str;
    auto res = _trans_op->encodeObject(object, str);
    if ( Success != res ) {
        return res;
    }
    auto etcd_res = _etcd_client.modify_if(obj_path+string("/")+object.name/*+string("/info")*/, str, revision).get();
    if ( (!etcd_res.is_ok()) && (etcd_res.error_code()!=0)) {
        return ETCDSetError;
    }
    return Success;
}

ErrorStatus CEtcdMetaDataIO::loadObject(const string& obj_name, Object& object, int& revision) {
    auto etcd_res = _etcd_client.get(obj_path+string("/")+obj_name).get();
    if ( !etcd_res.is_ok() ) {
        return ETCDGetError;
    }
    revision = etcd_res.value().modified_index();
    auto res = _trans_op->decodeObject(object, etcd_res.value().as_string());
    return res;
}

ErrorStatus CEtcdMetaDataIO::createBranch(const Branch& branch, int& revision) {
    string str;
    auto res = _trans_op->encodeBranch(branch, str);
    if ( Success != res ) {
        return res;
    }
    auto etcd_res = _etcd_client.add(string("/branches")+obj_path+string("/")+branch.obj_name+string("/")+branch.name, str).get();
    if ( !etcd_res.is_ok() ) {
        return ETCDAddError;
    }
    revision = etcd_res.value().created_index();
    return Success;
}

ErrorStatus CEtcdMetaDataIO::saveBranch(const Branch& branch, int revision, int& revision_new) {
    string str;
    auto res = _trans_op->encodeBranch(branch, str);
    if ( Success != res ) {
        return res;
    }
    auto etcd_res = _etcd_client.modify_if(string("/branches")+obj_path+string("/")+branch.obj_name+string("/")+branch.name, str, revision).get();
    if ( (!etcd_res.is_ok()) && (etcd_res.error_code()!=0)) {
        return ETCDSetError;
    }
    revision_new = etcd_res.index();
    return Success;
}

ErrorStatus CEtcdMetaDataIO::loadBranch(const string& obj_name, const string& br_name, Branch& branch, int& revision) {
    auto etcd_res = _etcd_client.get(string("/branches")+obj_path+string("/")+obj_name+string("/")+br_name).get();
    if ( !etcd_res.is_ok() ) {
        return ETCDGetError;
    }
    revision = etcd_res.value().modified_index();
    auto res = _trans_op->decodeBranch(branch, etcd_res.value().as_string());
    return res;
}

ErrorStatus CEtcdMetaDataIO::createNode(const Node& node, int& revision) {
    string str;
    auto res = _trans_op->encodeNode(node, str);
    if ( Success != res ) {
        return res;
    }
    auto etcd_res = _etcd_client.add(string("/nodes")+obj_path+string("/")+node.obj_name+string("/")+node.version, str).get();
    if ( !etcd_res.is_ok() ) {
        return ETCDAddError;
    }
    revision = etcd_res.value().created_index();
    return Success;
}

ErrorStatus CEtcdMetaDataIO::saveNode(const Node& node, int revision) {
    string str;
    auto res = _trans_op->encodeNode(node, str);
    if ( Success != res ) {
        return res;
    }
    auto etcd_res = _etcd_client.modify_if(string("/nodes")+obj_path+string("/")+node.obj_name+string("/")+node.version, str, revision).get();
    if ( (!etcd_res.is_ok()) && (etcd_res.error_code()!=0)) {
        return ETCDSetError;
    }
    return Success;
}

ErrorStatus CEtcdMetaDataIO::loadNode(const string& obj_name, const string& version, Node& node, int& revision) {
    auto etcd_res = _etcd_client.get(string("/nodes")+obj_path+string("/")+obj_name+string("/")+version).get();
    if ( !etcd_res.is_ok() ) {
        return ETCDGetError;
    }
    revision = etcd_res.value().modified_index();
    auto res = _trans_op->decodeNode(node, etcd_res.value().as_string());
    return res;
}

//CCephMetaDataIO::CCephMetaDataIO(const std::string& cluster_name, const std::string& user_name, const std::string& config_file_path, const std::string& pool_name) {
//    int ret = 0;
//    /* Declare the cluster handle and required variables. */
//    uint64_t flags ;

//    /* Initialize the cluster handle with the "ceph" cluster name and "client.admin" user */
//    {
//        ret = _cluster.init2(user_name.c_str(), cluster_name.c_str(), flags);
//        if (ret < 0) {
//            //            std::cerr << "Couldn't initialize the cluster handle! error " << ret << std::endl;
//            ret = EXIT_FAILURE;
//            throw "Ceph Init Error";
//        } else {
//            //            std::cout << "Created a cluster handle." << std::endl;
//        }
//    }

//    /* Read a Ceph configuration file to configure the cluster handle. */
//    {
//        ret = _cluster.conf_read_file(config_file_path.c_str());
//        if (ret < 0) {
//            //            std::cerr << "Couldn't read the Ceph configuration file! error " << ret << std::endl;
//            ret = EXIT_FAILURE;
//            //            throw "Ceph Conf Read Error";
//        } else {
//            //            std::cout << "Read the Ceph configuration file." << std::endl;
//        }
//    }
//    /* Connect to the cluster */
//    {
//        ret = _cluster.connect();
//        if (ret < 0) {
//            //            std::cerr << "Couldn't connect to cluster! error " << ret << std::endl;
//            ret = EXIT_FAILURE;
//            throw "Ceph Connect Error";
//        } else {
//            //            std::cout << "Connected to the cluster." << std::endl;
//        }
//    }

//    {
//        ret = _cluster.ioctx_create(pool_name.c_str(), _io_ctx);
//        if (ret < 0) {
//            //            std::cerr << "Couldn't set up ioctx! error " << ret << std::endl;
//            //exit(EXIT_FAILURE);
//            throw "Ceph IO Create Error";
//        } else {
//            //            std::cout << "Created an ioctx for the pool." << std::endl;
//        }
//    }
//}

//CCephMetaDataIO::~CCephMetaDataIO() {

//}

//ErrorStatus CCephMetaDataIO::begin(const string& obj_name) {
//    _io_ctx.lock_shared(obj_name, obj_name, obj_name, "", "", nullptr, 0);
//}

//ErrorStatus CCephMetaDataIO::commit(const string& obj_name) {
//    _io_ctx.unlock(obj_name, obj_name, obj_name);
//}

//ErrorStatus CCephMetaDataIO::rollback(const string& obj_name) {
//    _io_ctx.unlock(obj_name, obj_name, obj_name);
//}

//ErrorStatus CCephMetaDataIO::loadNode(const string& obj_name, const string& version, Node& node) {
//    librados::bufferlist fl;
//    int ret = _io_ctx.getxattr(obj_name, version.c_str(), fl);
//    if ( ret < 0 )
//        return DBSaveError;
//    auto res = decodeNode(node, fl.to_str());
//    return res;
//}

//ErrorStatus CCephMetaDataIO::saveNode(const Node& node) {
//    string node_info;
//    auto res = encodeNode(node, node_info);
//    if ( Success != res ) {
//        return res;
//    }
//    librados::bufferlist fl;
//    fl.append(node_info);
//    int ret = _io_ctx.setxattr(node.obj_name, node.version.c_str(), fl);
//    if ( ret < 0 )
//        return DBSaveError;
//    else {
//        return Success;
//    }
//}

//ErrorStatus CCephMetaDataIO::loadBranch(const string& obj_name, const string& br_name, Branch& branch) {
//    librados::bufferlist fl;
//    int ret = _io_ctx.getxattr(obj_name, br_name.c_str(), fl);
//    if ( ret < 0 )
//        return DBSaveError;
//    auto res = decodeBranch(branch, fl.to_str());
//    return res;
//}

//ErrorStatus CCephMetaDataIO::saveBranch(const Branch& branch) {
//    string branch_info;
//    auto res = encodeBranch(branch, branch_info);
//    if ( Success != res ) {
//        return res;
//    }
//    librados::bufferlist fl;
//    fl.append(branch_info);
//    int ret = _io_ctx.setxattr(branch.obj_name, branch.name.c_str(), fl);
//    if ( ret < 0 )
//        return DBSaveError;
//    else {
//        return Success;
//    }
//}

//ErrorStatus CCephMetaDataIO::loadObject(const string& obj_name, Object& object) {
//    int ret = 0;
//    librados::bufferlist read_buf;
//    unsigned long read_len = 4194304;
//    ret = _io_ctx.stat(obj_name, &read_len, nullptr);
//    if (ret < 0) {
//        return DBLoadError;
//    }

//    ret = _io_ctx.read(obj_name, read_buf, read_len, 0);
//    if (ret < 0) {
//        return DBLoadError;
//    }
//    return decodeObject(object, read_buf.to_str());
//}

//ErrorStatus CCephMetaDataIO::saveObject(const Object& object) {
//    string object_info;
//    auto res = encodeObject(object, object_info);
//    if ( Success != res ) {
//        return res;
//    }
//    librados::bufferlist fl;
//    fl.append(object_info);
//    int ret = _io_ctx.write_full(object.name, fl);
//    if ( ret < 0 )
//        return DBSaveError;
//    else {
//        return Success;
//    }
//}

//ErrorStatus CCephMetaDataIO::listObject(vector<string>& objects) {
//    auto iter = _io_ctx.nobjects_begin();
//    for ( ; iter != _io_ctx.nobjects_end(); iter++) {
//        objects.emplace_back(iter->get_oid());
//    }
//    return Success;
//}




}
